//=============================================================================
//     _   _                               ____                _  _          
//    | | | |  __ _  _ __   _ __   _   _  / ___|  _ __   _ __ (_)| |_   ___ 
//    | |_| | / _` || '_ \ | '_ \ | | | | \___ \ | '_ \ | '__|| || __| / _ \
//    |  _  || (_| || |_) || |_) || |_| |  ___) || |_) || |   | || |_ |  __/
//    |_| |_| \__,_|| .__/ | .__/  \__, | |____/ | .__/ |_|   |_| \__| \___|
//                  |_|    |_|     |___/         |_|                         
//
//                     HappySprite - We make sprites happy
//
// Copyright (c) 2007 by Tank Monkey Games
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//=============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

namespace HappySprite.Simulation
{
    public class Body
    {
        World owner;

        BodyTransform transform;

        Vector2 acceleration;
        float angularAcceleration;
        float angularVelocity;
        Vector2 biasedVelocity;
        float biasedAngularVelocity;
        //float previousAngularAcceleration;
        //Vector2 previousAcceleration;
        Vector2 velocity;

        float mass = 1f;
        float inverseMass = 1f;
        float momentOfIntertia = 1f;
        float inverseMomentOfIntertia = 1f;
        float elasticity;
        float friction;

        CollisionShape collisionShape = CollisionShape.Empty;
        CollisionDetails collisionDetails = CollisionDetails.None;
        CollisionResponse collisionResponse = CollisionResponse.None;

        int collisionGroup = 1;
        int collisionMask = 0xFFFF;

        BoundingRectangle worldLimits = BoundingRectangle.Empty;
        bool hasWorldLimits;

        BoundingRectangle bounds;
        bool boundsIsCurrent;

        public event GenericEventHandler<CollisionEventArgs> Collision;
        public event GenericEventHandler WorldLimitsExceeded;

        public Body()
        {
            transform = new BodyTransform(this);
        }

        public BoundingRectangle Bounds
        {
            get
            {
                if (!boundsIsCurrent)
                {
                    bounds = CollisionShape.GetBounds(Transform);
                    boundsIsCurrent = true;
                }

                return bounds;
            }
        }

        public CollisionDetails CollisionDetails
        {
            get 
            {
                if (collisionResponse == CollisionResponse.Physics)
                    return CollisionDetails.Full;

                return collisionDetails; 
            }
            set 
            { 
                collisionDetails = value; 
            }
        }

        public CollisionResponse CollisionResponse
        {
            get { return collisionResponse; }
            set { collisionResponse = value; }
        }

        public CollisionShape CollisionShape
        {
            get { return collisionShape; }
            set { collisionShape = value ?? CollisionShape.Empty; boundsIsCurrent = false; }
        }

        public int CollisionGroup
        {
            get { return collisionGroup; }
            set { collisionGroup = value; }
        }

        public int CollisionMask
        {
            get { return collisionMask; }
            set { collisionMask = value; }
        }

        public float Elasticity
        {
            get { return elasticity; }
            set { elasticity = value; }
        }

        public float Friction
        {
            get { return friction; }
            set { friction = value; }
        }

        public float Mass
        {
            get 
            { 
                return mass; 
            }
            set
            {
                // Check value
                if (value <= 0) throw new ArgumentException("Mass must be greater than zero.");

                mass = value;
                inverseMass = 1f / mass;
            }
        }

        public float MomentOfIntertia
        {
            get
            {
                return momentOfIntertia;
            }
            set
            {
                // Check value
                if (value <= 0) throw new ArgumentException("MomentOfIntertia must be greater than zero.");

                momentOfIntertia = value;
                inverseMomentOfIntertia = 1f / momentOfIntertia;
            }
        }

        public float AngularVelocity
        {
            get { return angularVelocity; }
            set { angularVelocity = value; }
        }

        public Vector2 Velocity
        {
            get { return velocity; }
            set { velocity = value; }
        }

        public float VelocityX
        {
            get { return velocity.X; }
            set { velocity.X = value; }
        }

        public float VelocityY
        {
            get { return velocity.Y; }
            set { velocity.Y = value; }
        }

        public BoundingRectangle WorldLimits
        {
            get 
            { 
                return worldLimits; 
            }
            set 
            { 
                worldLimits = value;
                hasWorldLimits = worldLimits != BoundingRectangle.Empty;
            }
        }

        public World Owner
        {
            get 
            { 
                return owner; 
            }
            set
            {
                if (owner == value)
                    return;

                if (value == null)
                    owner.Remove(this);
                else
                    value.Add(this);
            }
        }

        public Transform2D Transform
        {
            get { return transform; }
        }

        public void AddAcceleration(Vector2 acceleration)
        {
            this.acceleration += acceleration;
        }

        public void AddAngularAcceleration(float angularAcceleration)
        {
            this.angularAcceleration += angularAcceleration;
        }

        public void AddForce(Vector2 force)
        {
            this.acceleration += force * inverseMass;
        }

        public void AddTorque(float torque)
        {
            this.angularAcceleration += torque * inverseMomentOfIntertia;
        }

        /// <summary>
        /// Clamps to world limits.
        /// </summary>
        public void ClampToWorldLimits()
        {
            if (!hasWorldLimits)
                return;

            BoundingRectangle bounds = CollisionShape.GetBounds(transform);

            float dx = MathHelper.Max(0f, worldLimits.Min.X - bounds.Min.X) + MathHelper.Min(0f, worldLimits.Max.X - bounds.Max.X);
            float dy = MathHelper.Max(0f, worldLimits.Min.Y - bounds.Min.Y) + MathHelper.Min(0f, worldLimits.Max.Y - bounds.Max.Y);

            transform.PositionX += dx;
            transform.PositionY += dy;
        }

        public static bool CollisionCheck(Body left, Body right)
        {
            // Check group and masks
            return (left.CollisionGroup & right.CollisionMask) != 0
                && (right.CollisionGroup & left.CollisionMask) != 0;
        }

        public static CollisionDetails MinimumRequiredCollisionDetails(Body left, Body right)
        {
            return (CollisionDetails)Math.Max((int)left.CollisionDetails, (int)right.CollisionDetails);
        }

        /// <summary>
        /// Sets the world limits.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="bottom">The bottom.</param>
        /// <param name="right">The right.</param>
        /// <param name="top">The top.</param>
        public void SetWorldLimits(float left, float bottom, float right, float top)
        {
            WorldLimits = new BoundingRectangle(left, bottom, right, top);
        }

        protected virtual void OnCollision(CollisionEventArgs e)
        {
        }

        protected internal virtual void PerformWorldLimitsCheck()
        {
            if (!hasWorldLimits || CollisionShape == CollisionShape.Empty)
                return;

            if (worldLimits.Contains(Bounds) == ContainmentType.Contains)
                return;

            OnWorldLimitsExceeded();
        }

        protected virtual void OnWorldLimitsExceeded()
        {
            if (WorldLimitsExceeded != null)
                WorldLimitsExceeded();
        }

        internal float BiasedAngularVelocity
        {
            get { return biasedAngularVelocity; }
            set { biasedAngularVelocity = value; }
        }

        internal Vector2 BiasedVelocity
        {
            get { return biasedVelocity; }
            set { biasedVelocity = value; }
        }

        internal float InverseMass
        {
            get { return inverseMass; }
        }

        internal float InverseMomentOfIntertia
        {
            get { return inverseMomentOfIntertia; }
        }

        internal void AssignOwner(World owner)
        {
            this.owner = owner;
        }

        internal void HandleCollision(CollisionEventArgs e)
        {
            OnCollision(e);

            // Fire event
            if (Collision != null)
                Collision(e);
        }

        internal void OnTransformChanged()
        {
            boundsIsCurrent = false;

            if (owner != null)
                owner.OnTransformChanged(this);
        }

        //internal void GetVelocityComponents(ref Vector2 normal, out Vector2 vn, out Vector2 vt)
        //{
        //    Vector2 v = new Vector2(velocity.X, velocity.Y);
        //    Vector2 vDotN = Vector2.Dot(v, normal);

        //    vn = vDotN * normal;
        //    vt = v - vn;
        //}

        internal static void CollisionResponseHandler(CollisionEventArgs e)
        {
        //    WorldItem a = e.This;
        //    WorldItem b = e.Other;

        //    // Total inverse mass of the collision
        //    float sumInverseMass = a.inverseMass + b.inverseMass;

        //    // No change if both objects have infinite mass
        //    if (sumInverseMass <= 0f)
        //        return;

        //    // The coefficient of restitution
        //    float restitutionCoefficient = a.elasticity + b.elasticity;

        //    // The total friction is combined, but clamped to [0, 1]
        //    float frictionCoefficient = MathHelper.Clamp(a.friction + b.friction, 0f, 1f);

        //    // Compute the normal and tangential components of the collision
        //    Vector2 vnA1, vtA1, vnB1, vtB1;
        //    a.GetVelocityComponents(ref e.Normal, vnA1, vtA1);
        //    b.GetVelocityComponents(ref e.Normal, vnB1, vtB1);

        //    // Compute the impulse
        //    float impulseDenominator = Vector2.Dot(e.Normal, e.Normal * sumInverseMass);
        //    float impulseNumerator = (1f + te) * (vnB1 - vnA1);
        //    float j = jTop / jBottom;

        //    // Calculate the coefficient of restitution
        //    Vector2 vnA2 = vnA1 + 
        //    Vector2 vnB2 = 

        //    // Apply friction to the tangential component
        //    vtA *= totalFriction;
        //    vtB *= totalFriction;

        //    // Add the 

        //    // Minimum translational distance
        //    Vector2 mtd = e.Normal * e.Depth;

        //    // Adjust based on ratio of mass
        //    mtd *= inverseMass / sumInverseMass;

        //    // Update position
        //    Transform.PositionX += mtd.X;
        //    Transform.PositionY += mtd.Y;
        }

        //internal void Integrate(float dt, float hdt2)
        //{
        //    // Velocity Verlet integration formulas
        //    // x(t + dt) = x(t) + v(t) * dt + a(t) * hdt2
        //    // v(t + dt) = v(t) + 0.5 * (a(t) + a(t + dt)) * dt

        //    // Update position and velocity
        //    transform.Position += velocity * dt + previousAcceleration * hdt2;
        //    velocity += 0.5f * (previousAcceleration + acceleration) * dt;

        //    // Update rotation and angular velocity
        //    transform.Rotation += angularVelocity * dt + previousAngularAcceleration * hdt2;
        //    angularVelocity += 0.5f * (previousAngularAcceleration + angularAcceleration) * dt;

        //    // Set for next step
        //    previousAcceleration = acceleration;
        //    previousAngularAcceleration = angularAcceleration;

        //    // Zero the forces/acceleration
        //    acceleration = Vector2.Zero;
        //    angularAcceleration = 0f;
        //}

        internal void IntegrateForces(float dt)
        {
            if (inverseMass > 0f)
            {
                velocity += dt * acceleration;
                angularVelocity += dt * angularAcceleration;
            }

            // Zero the forces/acceleration
            acceleration = Vector2.Zero;
            angularAcceleration = 0f;

            // Zero the biased velocities
            biasedVelocity = Vector2.Zero;
            biasedAngularVelocity = 0f;
        }

        internal void IntegrateVelocities(float dt)
        {
            transform.Position += dt * (velocity + biasedVelocity);
            transform.Rotation += dt * (angularVelocity + biasedAngularVelocity);
        }

        internal void ApplyImpulse(Vector2 impulse, Vector2 delta)
        {
            velocity += inverseMass * impulse;
            angularVelocity += inverseMomentOfIntertia * Vector2Extensions.Cross(delta, impulse);
        }

        #region Custom Data

        object tag;

        public object Tag
        {
            get { return tag; }
            set { tag = value; }
        }

        public T GetTag<T>()
        {
            return (T)tag;
        }

        #endregion
    }
}
